package opr.sgb.cs.purdue.edu;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;

import global.sgb.cs.purdue.edu.AttrType;
import global.sgb.cs.purdue.edu.L2distance;
import global.sgb.cs.purdue.edu.Tuple;
import group.cs.purdue.edu.ArrayListGroups;
import group.cs.purdue.edu.Group;
import group.cs.purdue.edu.Groups;
import group.cs.purdue.edu.RtreeGroups;

/**
 * the sgb-all operation that iterate the tuples sets
 * @author tang49
 *
 */
public class GroupAllOpr extends GroupbyOperator {

	public String GroupsStorageName;
	Groups Gstorage;
	
	public GroupAllOpr() {
		// TODO Auto-generated constructor stub
	}
	
	public Groups getGstorage() {
		return Gstorage;
	}

	public void setGstorage(String gstorname) {
		
		   
		 if(gstorname.equals("arraylist"))
		 {
			 this.Gstorage=new ArrayListGroups();
			 
		 }else if (gstorname.equals("rtree"))
		 {
			 this.Gstorage=new RtreeGroups();
			 
		 }else if (gstorname.equals("other"))
		 {
			 throw new IllegalStateException("invalid group index");
			 
		 }else
		 {
			 throw new IllegalStateException("invalid group index");
		 }
			
	}

	
	/**
	 * 
	 * @param DistanceMetric
	 * @param overlap
	 * @param indexongroups
	 * @param eps
	 * @param Fileds
	 * initi the opertaor for the SGB-All operation 
	 */
	public void iniEnv(
			int DistanceMetric, 
			int overlap,
			String indexongroups,
			double eps,
			int []Fileds)
	{
		
		this.setDistanceMetric(DistanceMetric);
		//set up the distance metric
		
		this.setOverlap(overlap);
		
		//set up the index for the groups
		this.setGstorage(indexongroups);
		
		//set up the eps
		this.setEps(eps);
		this.Gstorage.setEPS(eps);
		
		/* set up the distance metric*/
		this.setDistanceMetric(DistanceMetric);
		this.Gstorage.setDistanceMetric(DistanceMetric);
		
		this.setFileds(Fileds);
	}
	/**
	 * 
	 */
	@Override
	public void OprCurrent(Tuple T)
	{
		//check the intersection of the tuple with groups 
		ArrayList<Group>groups
		=this.Gstorage.intersection(T, this.getFileds());
		
		boolean joinsucc=false; 
		//if there there is intersection, 
		boolean updateUniqureForgroups=false;
		
		//we can update this one to Tire-Tree
		HashMap <String, TreeMap<Integer, Tuple>>UniqueTforGroups
		=new HashMap<String, TreeMap<Integer, Tuple>>();
		
		ArrayList<String>  AlreadyInGroups= new ArrayList<String>();
		
		if(groups.size()>0)
		{
			ArrayList<Group>tmp= new ArrayList<Group>();
			
			// for every intersection, judge whether this tuple can be joined into this group or not
			for(Group g :groups)
			{
				TreeMap<Integer, Tuple> tuplesinsidebound= new TreeMap<Integer, Tuple>();
				boolean inside=g.judgeJoinGroup(T, this.getFileds(), tuplesinsidebound);
				
				//if new tuple can join in one exist group
				if(inside)
				{
					joinsucc=true;
					
					//add this one to that group
					//shrink the group 
					if(this.getOverlap()==AttrType.ODUPLICATIED)
					{
						int pkey=(Integer)T.getFiled(0);
						g.add(pkey, T);
						String groupsString=g.toTupleString();
						AlreadyInGroups.add(groupsString);
						//UniqueTforGroups.put(groupsString, null);
						
					}else if(this.getOverlap()==AttrType.OANY)
					{
						tmp.add(g);
						
					}else if(this.getOverlap()==AttrType.OELIMINATED)
					{
						//tmp.add(g);
						
					}
						
					//g.add(id, t);
				}else if(tuplesinsidebound.size()>0)
					//this tuple is not belong to the any new group, 
					//but it contains some intersection
				{
					 /******************************************************/
					 /**check the duplicated pints in the bounds*/
					
					boolean filterSucc=
							FilterCliqueInGroups(tuplesinsidebound, AlreadyInGroups);
					
					if(!filterSucc){
							updateUniqueTforGroups(UniqueTforGroups,tuplesinsidebound);
					}
					 
				
				}
				
			}
			
		}//if
		
		//iterate the HashMap to generate all new groups
		if(UniqueTforGroups.size()>0)
		{
			FilterTuplesInGroups(UniqueTforGroups, AlreadyInGroups);
			//check here again
			if(UniqueTforGroups.size()>0){
				addNewGroups(UniqueTforGroups,T);
				joinsucc=true;
			}
		}
	
		//if there is not intersection or can not join in any groups 
		//build one group and add this tuple into this group, 
		//add group into the gstorage
	    if(joinsucc==false||groups.size()<=0)
			{
	    	  int id=this.Gstorage.getCount()+1;
	          boolean updatecenter=true;
	          boolean updatebound=false;
	          
	          //init the group information
	    	  Group g= new Group(id, updatecenter,updatebound, 
	    			  this.getDistanceMetric(),
	    			  this.getFileds(),
	    			  this.getEps());
	    	  
	    	  g.add(T);
	          //we need to add the duplicated points into the new groups 
	          
	          this.Gstorage.addgroup(g);
			  
			}//if
		
	}
	
	private void FilterTuplesInGroups(
			HashMap<String, TreeMap<Integer, Tuple>> uniqueTforGroups,
			ArrayList<String> alreadyInGroups) {
		// TODO Auto-generated method stub
		
		Iterator<Entry<String, TreeMap<Integer, Tuple>>> entries = uniqueTforGroups.entrySet().iterator();
		
		while (entries.hasNext()) {
			  Map.Entry<String, TreeMap<Integer, Tuple>> entry = entries.next();
			  String tupleStrings=entry.getKey();
			  
			  for(String ingroups:alreadyInGroups)
			  {
				  
				  int statues=compareTwoSets(tupleStrings, ingroups);
				  
				  //update this string
				  //ingroups is xxxyyy
				  //tupleStrings is xxx
				  if(statues==1)
				  {	
					  System.out.println("FilterTuplesInGroups problems");
					  
				  }else if(statues==2)
				  {
					  entries.remove();
				  }
				  
			  }
		
		}
	}

	private boolean FilterCliqueInGroups(
			TreeMap<Integer, Tuple> tuplesinsidebound,
			ArrayList<String> alreadyInGroups) {
		// TODO Auto-generated method stub
		
		tupleSets tmps =new tupleSets(tuplesinsidebound);
		
		String Inputcliquestring=tmps.toString();
		
		for(String groupsString:alreadyInGroups)
		{
			
			 int statues=compareTwoSets(Inputcliquestring, groupsString);
			 
			 if(statues==1)
			 {
				 System.out.println("Problem in wrong, FilterCliqueInGroups");
				 
			 }else if(statues==2)
			 {
				 return true;
			 }
			
		}
		
		return false;
	}

	/**
	 * 
	 * @param uniqueTforGroups
	 * @param t
	 */
	private void addNewGroups(
			HashMap<String,
			TreeMap<Integer, Tuple>> uniqueTforGroups, 
			Tuple t) {
		// TODO Auto-generated method stub
		
		Iterator<Entry<String, TreeMap<Integer, Tuple>>> entries 
		= uniqueTforGroups.entrySet().iterator();
		
		while (entries.hasNext()) {
			  Map.Entry<String, TreeMap<Integer, Tuple>> entry = entries.next();
			  
			  TreeMap<Integer, Tuple> tuplesets=entry.getValue();
			  
			  if(tuplesets==null)
				  continue;
			  
			  addNewGroup(t,tuplesets);
			  
		}
		
	}

	private void addNewGroup(
			Tuple T, 
			TreeMap<Integer,Tuple> tuplesinsidebound) {
		
		 int id=this.Gstorage.getCount()+1;
         boolean updatecenter=true;
         boolean updatebound=false;
		 //init the group information
   	  	 Group ng= new Group(id, updatecenter,updatebound, 
   			  this.getDistanceMetric(),
   			  this.getFileds(),
   			  this.getEps());
   	  
   	  	ng.add(T);
   	  
		 if(this.getOverlap()==AttrType.ODUPLICATIED)
			{
			 /**add duplicated tuples into the new groups*/
			
			 Iterator<Entry<Integer, Tuple>> entries = 
					 tuplesinsidebound.entrySet().iterator();
				while (entries.hasNext()) {
					  Map.Entry<Integer, Tuple> entry = entries.next();
					 Tuple t2=entry.getValue();
					 ng.add(t2);
				}
				
			 
			}
		 this.Gstorage.addgroup(ng);
		// TODO Auto-generated method stub
		
	}

	/**
	 * we need to use the Tire in the Future. and put the tupleinsidebound into the clique sets
	 * @param UniqueTforGroups
	 * @param tuplesinsidebound
	 * @return
	 */
	private int updateUniqueTforGroups(
			HashMap <String, TreeMap<Integer, Tuple>>UniqueTforGroups,
			TreeMap<Integer,Tuple> tuplesinsidebound) {
		// TODO Auto-generated method stub
		
		int statue=0;
		tupleSets tmps =new tupleSets(tuplesinsidebound);
		String cliquestring=tmps.toString();
	
		//check this treemap whether contain the string
		if(UniqueTforGroups.containsKey(cliquestring))
		{
			return statue;
		}else
		{
			Iterator<Entry<String, TreeMap<Integer, Tuple>>> entries = UniqueTforGroups.entrySet().iterator();
			boolean firstTime=true;
			boolean nointersection=true;
			
			while (entries.hasNext()) {
				
				  Map.Entry<String, TreeMap<Integer, Tuple>> entry = entries.next();
				 
				  String tmp=entry.getKey();
				  int statues=compareTwoSets(cliquestring, tmp);
				  
				  //update this string
				  //cliquestring is xxxyyy
				  //tmp is xxx
				  if(statues==1)
				  {	
					  nointersection=false;
					  if(firstTime)
					  {
						  //update the current string
						  firstTime=false;
						  entries.remove();
						  UniqueTforGroups.put(cliquestring, tuplesinsidebound);
						  
					  }else
					  {
						  //delete this group candidates
						  entries.remove();
					  }
				  }else if(statues==2)
					  //cliquestring is xxx
					  //tmp is xxxyy
				  {
					  return statues;
				  }
			}//while
			
			//add this group candidates into the HashMap
			if(nointersection)
			{
				UniqueTforGroups.put(cliquestring, tuplesinsidebound);
			}
			
		}//else
		
		
		return statue;
	}

	public void PrintOutputs()
	{
		System.out.println(this.Gstorage.toString());
		
	}
	
	/**
	 * if return 1, x contain y
	 * if return 2, y contain x. 
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	public int compareTwoSets(String x, String y)
	{
		int statue=0;
		
		if(x.equals(y))
		{
			return 3;
		}
		
		if(x.length()>y.length())
		{
			String tmp[]=y.split(",");
			
			for(String yy:tmp)
			{
				if(!x.contains(yy))
				{
					return 0;
				}
			}
			
			return 1;
		}else if (x.length()<y.length())
		{
			String tmp[]=x.split(",");
			
			for(String xx:tmp)
			{
				if(!y.contains(xx))
				{
					return 0;
				}
			}
			
			return 2;
		}
		
		return statue;
		
	}
	
	
	
	private class tupleSets
	{
		TreeMap<Integer,Tuple> tuplesinsidebound;

		tupleSets(TreeMap<Integer,Tuple> tuplesinsidebound)
		{
			this.tuplesinsidebound=tuplesinsidebound;
		}
		@Override
		public boolean equals(Object obj) {
			// TODO Auto-generated method stub
			TreeMap<Integer,Tuple> newone= (TreeMap<Integer,Tuple>)obj;
			
			Iterator<Entry<Integer, Tuple>> entries = tuplesinsidebound.entrySet().iterator();
			while (entries.hasNext()) {
				  
			      Map.Entry<Integer, Tuple> entry = entries.next();
			      int key=entry.getKey();
			      
			      if(!newone.containsKey(key))
			      {
			    	  return false;
			      }
				
			}
			
			return true;
		}

		@Override
		public int hashCode() {
			// TODO Auto-generated method stub
			int prime=31;
			
			Iterator<Entry<Integer, Tuple>> entries = tuplesinsidebound.entrySet().iterator();
			while (entries.hasNext()) {
				  
			      Map.Entry<Integer, Tuple> entry = entries.next();
				  
				  prime=prime+entry.getKey();
			}
			
			return prime;
			
			
		}

		@Override
		public String toString() {
			// TODO Auto-generated method stub
			StringBuffer tmp= new StringBuffer();
			
			Iterator<Entry<Integer, Tuple>> entries = tuplesinsidebound.entrySet().iterator();
			while (entries.hasNext()) {
				  Map.Entry<Integer, Tuple> entry = entries.next();
				 Tuple t2=entry.getValue();
				 
				 tmp.append(String.valueOf(t2.getFiled(0))+",");
			}
			
			return tmp.toString();
		}
		
		
	}
	

}
